"""
Case Type   : 页面修复功能测试
Case Name   : 索引文件页面损坏及修复测试
Create At   : 2025/09/16
Owner       : test_user
Description :
    1、检查页面修复参数是否开启
    2、创建表并插入数据，为表建立索引
    3、查询索引文件所在位置
    4、在备机构造坏块
    5、查看备机页面损坏情况
    6、主机修改数据使得备机回放
    7、查看备机页面修复情况
Expect      :
    1、页面修复参数已开启
    2、表创建、数据插入和索引创建成功
    3、成功获取索引文件路径
    4、坏块构造成功
    5、页面确认损坏
    6、数据修改成功触发回放
    7、页面修复成功，索引可正常使用
History     :
"""

import unittest
import os
import time
from yat.test import macro
from yat.test import Node
from testcase.utils.Logger import Logger
from testcase.utils.CommonSH import CommonSH
from testcase.utils.Constant import Constant

logger = Logger()
COMMONSH = CommonSH("PrimaryDbUser")
STANDBY_SH = CommonSH("Standby1DbUser")
pri_node = Node("PrimaryDbUser")
standby_node = Node("Standby1DbUser")

class IndexPageRepairTestCase(unittest.TestCase):
    def setUp(self):
        logger.info("==Opengauss_Function_Guc_Page_Repair_Case0009测试开始==")
        self.constant = Constant()
        # 检查数据库集群状态
        is_started = COMMONSH.get_db_cluster_status()
        self.assertTrue("Degraded" in is_started or "Normal" in is_started)

    def verify_parameter_value(self, expected_value, dbname=None):
        """验证参数值是否正确"""
        sql_cmd = "show page_repair_function;"
        result = COMMONSH.execut_db_sql(sql_cmd, dbname=dbname)
        logger.info(f"参数查询结果: {result}")

        # 统一转换为小写便于处理
        result_lower = result.lower()

        # 先尝试简单匹配常见格式
        if f"page_repair_function | {expected_value}" in result_lower:
            logger.info(f"参数值验证成功: {expected_value}")
            return True

        # 解析参数值的简化逻辑
        actual_value = self._parse_parameter_value(result)

        if actual_value is None:
            self.fail(f"无法解析参数值，查询结果: {result}")

        logger.info(f"解析到的参数值: {actual_value}, 期望值: {expected_value}")
        self.assertEqual(actual_value, expected_value, 
                       f"参数值应为{expected_value}，但实际为{actual_value}")
        return True

    def _parse_parameter_value(self, result):
        """解析参数值的辅助函数"""
        lines = result.strip().split('\n')

        for line in lines:
            line = line.strip().lower()
        
            # 跳过无关行
            if not line or '----' in line or '(1 row)' in line:
                continue
        
            # 直接提取参数值
            value = self._extract_value_from_line(line)
            if value:
                return value

        # 如果上面没找到，尝试全局查找
        return self._find_value_in_result(result.lower())

    def _extract_value_from_line(self, line):
        """从单行文本中提取参数值"""
        # 格式: page_repair_function | off
        if 'page_repair_function' in line and '|' in line:
            parts = line.split('|')
            if len(parts) > 1:
                return parts[1].strip()

        # 格式: page_repair_function off
        if 'page_repair_function' in line:
            parts = line.split()
            if len(parts) > 1:
                return parts[1].strip()

        # 格式: off (单独一行)
        if line in ['on', 'off']:
            return line

        return None

    def _find_value_in_result(self, result_lower):
        """在整个结果中查找参数值"""
        if ' off' in result_lower and ' on' not in result_lower.replace('page_repair_function', ''):
            return 'off'
        elif ' on' in result_lower and ' off' not in result_lower.replace('page_repair_function', ''):
            return 'on'
        return None

    def test_index_page_repair(self):
        # 步骤1：检查页面修复参数是否开启
        logger.info("步骤1：开启页面修复参数")
        result = COMMONSH.execute_gsguc("reload",
                                        self.constant.GSGUC_SUCCESS_MSG,
                                        "page_repair_function=on",
                                        node_name="all")
        self.assertTrue(result)
        
        # 重启集群使参数生效
        COMMONSH.restart_db_cluster()
        status = COMMONSH.get_db_cluster_status()
        self.assertTrue("Degraded" in status or "Normal" in status)
        time.sleep(5)
        
        # 验证参数已开启
        self.verify_parameter_value("on")

        # 步骤2：创建表并插入数据，为表建立索引
        logger.info("步骤2：创建表并插入数据，为表建立索引")
        sql_cmd = """
            drop table if exists index_t;
            create table index_t(a int, b char(50)) with (segment=on);
            insert into index_t (a, b) select generate_series(1,500), 'aaa';
            create index a_index on index_t (a);
        """
        result = COMMONSH.execut_db_sql(sql_cmd)
        logger.info(result)
        self.assertIn(self.constant.CREATE_TABLE_SUCCESS, result)
        self.assertIn("INSERT", result)
        self.assertIn("CREATE INDEX", result)

        # 验证数据插入成功
        sql_cmd = "select count(*) from index_t;"
        result = COMMONSH.execut_db_sql(sql_cmd)
        logger.info(f"数据插入验证: {result}")
        self.assertIn("500", result)

        # 验证索引创建成功
        sql_cmd = "select * from pg_indexes where indexname = 'a_index';"
        result = COMMONSH.execut_db_sql(sql_cmd)
        logger.info(f"索引创建验证: {result}")
        self.assertIn("a_index", result)

        # 步骤3：查询索引文件所在位置
        logger.info("步骤3：查询索引文件所在位置")
        sql_cmd = "select pg_relation_filepath('a_index');"
        result = COMMONSH.execut_db_sql(sql_cmd)
        logger.info(result)
        self.assertIn("base/", result)
        file_path = result.strip().split('\n')[-2].strip()
        self.file_name = os.path.basename(file_path)
        self.dir_path = os.path.dirname(file_path)
        logger.info(f"索引文件: {self.file_name}, 目录: {self.dir_path}")

        # 步骤4：在备机构造坏块
        logger.info("步骤4：在备机构造坏块")
        # 获取PGDATA路径
        pgdata_path = macro.DB_INSTANCE_PATH
        
        # 构造坏块命令
        damage_cmds = [
            f"cd {pgdata_path}/{self.dir_path}",
            f"cp {self.file_name} {self.file_name}.bak",  # 备份原文件
            "dd if=/dev/zero of=allzeros bs=8192 count=1",
            f"dd if=allzeros of=./{self.file_name} bs=8192 seek=1 count=1 conv=notrunc",
            f"ls -la {self.file_name}*"  # 查看文件信息
        ]
        
        for cmd in damage_cmds:
            result = standby_node.sh(cmd).result()
            logger.info(f"执行命令: {cmd}, 结果: {result}")

        # 重启集群使数据落盘
        COMMONSH.restart_db_cluster()
        status = COMMONSH.get_db_cluster_status()
        self.assertTrue("Degraded" in status or "Normal" in status)
        time.sleep(5)

        # 步骤5：查看备机页面损坏情况
        logger.info("步骤5：查看备机页面损坏情况")
        # 尝试使用pagehack检查页面状态
        try:
            pagehack_cmd = f"cd {pgdata_path}/{self.dir_path}; pagehack -f {self.file_name}"
            result = standby_node.sh(pagehack_cmd).result()
            logger.info(f"页面检查结果: {result}")
            # 检查是否包含损坏信息
            if any(keyword in result.lower() for keyword in ["damage", "error", "corrupt", "invalid"]):
                logger.info("索引页面确认损坏")
            else:
                logger.warning("页面检查工具未显示明确损坏信息")
        except Exception as e:
            logger.info(f"使用pagehack检查页面状态时出错: {e}")

        # 验证索引是否受损
        logger.info("验证索引是否受损")
        sql_cmd = "select * from index_t where a = 50;"
        try:
            result = STANDBY_SH.execut_db_sql(sql_cmd)
            logger.info(f"备机索引查询结果: {result}")
            # 如果查询失败或返回异常，说明索引受损
            if "50" not in result or "error" in result.lower():
                logger.info("索引受损确认")
        except Exception as e:
            logger.info(f"备机索引查询异常（预期）: {e}")

        # 步骤6：主机修改数据使得备机回放
        logger.info("步骤6：主机修改数据使得备机回放")
        sql_cmd = "update index_t set b = 'bbb' where a = 50;"
        result = COMMONSH.execut_db_sql(sql_cmd)
        logger.info(f"主机更新结果: {result}")
        self.assertIn("UPDATE", result)

        # 再次更新确保触发回放
        sql_cmd = "update index_t set b = 'ccc' where a = 51;"
        result = COMMONSH.execut_db_sql(sql_cmd)
        logger.info(f"主机第二次更新结果: {result}")
        self.assertIn("UPDATE", result)

        # 等待备机回放完成
        logger.info("等待备机回放完成...")
        time.sleep(20)

        # 步骤7：查看备机页面修复情况
        logger.info("步骤7：查看备机页面修复情况")
        
        # 验证索引功能是否恢复
        sql_cmd = "select * from index_t where a = 50;"
        result = STANDBY_SH.execut_db_sql(sql_cmd)
        logger.info(f"备机修复后索引查询结果: {result}")
        self.assertIn("50", result, "索引功能修复失败")
        self.assertIn("bbb", result, "索引功能修复失败")

        # 验证完整数据
        sql_cmd = "select count(*) from index_t;"
        result = STANDBY_SH.execut_db_sql(sql_cmd)
        logger.info(f"备机数据总数: {result}")
        self.assertIn("500", result, "数据不完整")

        # 验证索引扫描功能
        sql_cmd = "explain select * from index_t where a between 45 and 55;"
        result = STANDBY_SH.execut_db_sql(sql_cmd)
        logger.info(f"索引扫描计划: {result}")
        self.assertIn("Index Scan", result, "索引扫描功能异常")

    def tearDown(self):
        logger.info("恢复环境")
        
        # 清理测试表（会自动清理索引）
        sql_cmd = "drop table if exists index_t;"
        COMMONSH.execut_db_sql(sql_cmd)
        
        # 在备机恢复备份的文件（如果存在）
        try:
            pgdata_path = macro.DB_INSTANCE_PATH
            if hasattr(self, 'dir_path') and hasattr(self, 'file_name'):
                restore_cmd = f"cd {pgdata_path}/{self.dir_path}; [ -f {self.file_name}.bak ] && mv {self.file_name}.bak {self.file_name} || true"
                standby_node.sh(restore_cmd)
                logger.info("备机文件恢复完成")
        except Exception as e:
            logger.info(f"恢复备份文件时出错: {e}")

        # 恢复页面修复参数为默认值
        result = COMMONSH.execute_gsguc("reload",
                       self.constant.GSGUC_SUCCESS_MSG,
                        "page_repair_function=off",
                        node_name="all")
        
        # 检查集群状态
        is_started = COMMONSH.get_db_cluster_status()
        self.assertTrue("Degraded" in is_started or "Normal" in is_started)
        logger.info("==Opengauss_Function_Guc_Page_Repair_Case0009测试结束==")